package com.fezs.web.mvc.handle;

import cn.hutool.core.text.StrPool;
import com.fezs.common.constants.ErrorCode;
import com.fezs.common.context.BaseContextHandler;
import com.fezs.common.exception.BusinessException;
import com.fezs.common.exception.ServerException;
import com.fezs.common.model.response.ResultBody;
import com.fezs.message.entity.MsgEntity;
import com.fezs.message.template.MessageTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.xml.bind.ValidationException;
import java.util.stream.Collectors;

/**
 * 全局异常处理器，将 Exception 翻译成 CommonResult + 对应的异常编号
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @Value("${spring.application.name}")
    private String appName;

    @Value("${spring.profiles.active}")
    private String profile;

    @Autowired(required = false)
    private MessageTemplate messageTemplate;

    /**
     * 处理所有异常，主要是提供给 Filter 使用
     * 因为 Filter 不走 SpringMVC 的流程，但是我们又需要兜底处理异常，所以这里提供一个全量的异常处理过程，保持逻辑统一。
     *
     * @param request 请求
     * @param ex 异常
     * @return 通用返回
     */
    public ResultBody<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {
        if (ex instanceof MissingServletRequestParameterException) {
            return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
        }
        if (ex instanceof MethodArgumentTypeMismatchException) {
            return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);
        }
        if (ex instanceof BindException) {
            return bindExceptionHandler((BindException) ex);
        }
        if (ex instanceof ConstraintViolationException) {
            return constraintViolationExceptionHandler((ConstraintViolationException) ex);
        }
        if (ex instanceof ValidationException) {
            return validationException((ValidationException) ex);
        }
        if (ex instanceof AuthenticationException) {
            return authenticationExceptionHandler(request, (AuthenticationException) ex);
        }
        if (ex instanceof AccessDeniedException) {
            return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
        }
        if (ex instanceof BusinessException) {
            return businessExceptionHandler(request, (BusinessException) ex);
        }
        if (ex instanceof ServerException) {
            return serverExceptionHandler(request, (ServerException) ex);
        }
        return defaultExceptionHandler(request, ex);
    }

    /**
     * 处理 SpringMVC 请求参数缺失
     *
     * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ResultBody<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        log.warn("[missingServletRequestParameterExceptionHandler]", ex);
        return ResultBody.buildFailResult(ErrorCode.BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     *
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResultBody<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
        log.warn("[methodArgumentTypeMismatchExceptionHandler]", ex);
        return ResultBody.buildFailResult(ErrorCode.BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
    }

    /**
     * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    public ResultBody<?> bindExceptionHandler(BindException ex) {
        log.warn("[handleBindException]", ex);
        FieldError fieldError = ex.getFieldError();
        assert fieldError != null; // 断言，避免告警
        return ResultBody.buildFailResult(ErrorCode.BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ResultBody<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {
        log.warn("[constraintViolationExceptionHandler]", ex);
        String msg = ex.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(StrPool.COMMA));
        return ResultBody.buildFailResult(ErrorCode.BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", msg));
    }

    /**
     * 抛出的 ValidationException 异常
     */
    @ExceptionHandler(value = ValidationException.class)
    public ResultBody<?> validationException(ValidationException ex) {
        log.warn("[constraintViolationExceptionHandler]", ex);
        return ResultBody.buildFailResult(ErrorCode.BAD_REQUEST);
    }

    /**
     * 处理 Spring Security 登录的异常
     */
    @ExceptionHandler(value = AuthenticationException.class)
    public ResultBody<?> authenticationExceptionHandler(HttpServletRequest req, AuthenticationException ex) {
        log.warn("[authenticationExceptionHandler][没有登录信息 url({})]", req.getRequestURL());
        return ResultBody.buildFailResult(ErrorCode.UNAUTHORIZED);
    }

    /**
     * 处理 Spring Security 权限不足的异常
     *
     * 来源是，使用 @PreAuthorize 注解，AOP 进行权限拦截
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public ResultBody<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
        log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", BaseContextHandler.getUserId(), req.getRequestURL());
        return ResultBody.buildFailResult(ErrorCode.FORBIDDEN);
    }

    /**
     * 处理业务异常
     *
     * 例如说，商品库存不足，用户手机号已存在。
     */
    @ExceptionHandler(value = BusinessException.class)
    public ResultBody<?> businessExceptionHandler(HttpServletRequest req, BusinessException ex) {
        log.error("[businessExceptionHandler]", ex);
        return ResultBody.buildFailResult(ex.getCode(), ex.getMessage());
    }

    /**
     * 服务异常
     */
    @ExceptionHandler(value = ServerException.class)
    public ResultBody<?> serverExceptionHandler(HttpServletRequest req, ServerException ex) {
        log.error("[serverExceptionHandler]", ex);
        this.sendWxNotice(req, ex);
        return ResultBody.buildFailResult(ex.getCode(), ex.getMessage());
    }

    /**
     * 处理系统异常，兜底处理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public ResultBody<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
        log.error("[defaultExceptionHandler]", ex);
        this.sendWxNotice(req, ex);
        return ResultBody.buildFailResult(ErrorCode.INTERNAL_SERVER_ERROR);
    }

    /**
     * 发送微信消息
     */
    private void sendWxNotice(HttpServletRequest req, Throwable ex) {
        if (messageTemplate != null) {
            String content = MsgEntity.getWebMarkdownMsg(appName, profile, req.getRequestURI(), ex);
            messageTemplate.send(MsgEntity.buildMarkdownMsg(content));
        }
    }

}
